#include "NodeService.h"
#include <iostream>

#include <vector>
#include <vector>
#include <string>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <getopt.h>
#include <stdarg.h>
#include "WrappedNode.h"

using namespace apache::thrift;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;

// the name space specified in the thrift file
using namespace mp2;
using namespace std;

char *args[18];

/*
 * Add Node 
 *
 * Helper function that launches a node with the necessary args
 *
 */
void addNode(int m, char* id, int port, int introPort, 
        const char* logconffile, int stabilizeInterval, int fixInterval){
    int i;

    for(i = 0; i < 18; i++){
        args[i] = (char *) malloc(sizeof(int));
    }

    i = 0;
    args[i++] = (char *) "./node";
    args[i++] = (char *) "--m";
    sprintf(args[i++], "%d", m);
    args[i++] = (char *) "--id";
    args[i++] = id;
    args[i++] = (char *) "--port";
    sprintf(args[i++], "%d", port);
    args[i++] = (char *) "--introducerPort";
    sprintf(args[i++], "%d", introPort);

    if(logconffile){
        args[i++] = (char *) "--logConf";
        args[i++] = (char *) logconffile;
    }

    if(stabilizeInterval != -1){
        args[i++] = (char *) "--stabilizeInterval";
        sprintf(args[i++], "%d", stabilizeInterval);
    }

    if(fixInterval != -1){
        args[i++] = (char *) "--fixInterval";
        sprintf(args[i++], "%d", fixInterval);
    }

    args[i] = NULL;
    execv("./node", args);
    printf("Error: %s\n", strerror(errno));
}


/*
 * Main
 *
 * Takes all options given by user
 * Launches introducer node
 * Waits for incoming commands from user
 *
 */
int main(int argc, char **argv) {
    int port, opt, long_index;
    int m = -1, introPort = -1, attachToNode = -1, stabilizeInterval = -1, fixInterval = -1;
    const char* logconffile = NULL;
    char input[100];
    char command[10] ;
    char* id;
    srand(time(NULL));

    struct option long_options[] = {
        {"m", required_argument, 0, 1000},
        {"startingPort", required_argument, 0, 1001},
        {"attachToNode", required_argument, 0, 1002},
        {"stabilizeInterval", required_argument, 0, 1003},
        {"fixInterval", required_argument, 0, 1004},
        {"logConf", required_argument, 0, 1005},
        {0, 0, 0, 0},
    };

    while ((opt = getopt_long(argc, argv, "", long_options, &long_index)) != -1)
    {
        switch (opt) {
            case 0:
                if (long_options[long_index].flag != 0) {
                    break;
                }
                printf("option %s ", long_options[long_index].name);
                if (optarg) {
                    printf("with arg %s\n", optarg);
                }
                printf("\n");
                break;

            case 1000:
                m = strtol(optarg, NULL, 10);
                //cout << m << "\n";
                assert((m >= 3) && (m <= 10));
                break;

            case 1001:
                introPort = strtol(optarg, NULL, 10);
                //cout << id << "\n";
                //assert(id >= 0);
                break;

            case 1002:
                attachToNode = strtol(optarg, NULL, 10);
                //cout << port << "\n";
                //assert(port > 0);
                break;

            case 1003:
                stabilizeInterval = strtol(optarg, NULL, 10);
                //cout << stabilizeInterval << "\n";
                //assert(introducerPort > 0);
                break;

            case 1004:
                fixInterval = strtol(optarg, NULL, 10);
                //cout << fixInterval << "\n";
                break;

            case 1005:
                logconffile = optarg;
                //assert(stabilizeInterval > 0);
                break;

            default:
                exit(1);
        }
    }

    if(m == -1){
        printf("Please select a value for m.\n");
        exit(1);
    }


    // Create the introducer node at random port or assigned port
    if(attachToNode == -1){
        if(introPort == -1)
            introPort = rand() % 10000 + 49152;
        if(fork() == 0){
            addNode(m, (char *) "0", introPort, introPort, NULL, stabilizeInterval, fixInterval);
        }
        printf("Creating introducer at port %d\n", introPort);

        sleep(1);
    } else{ // Or attach myself to another existing node
        introPort = attachToNode;
    }

    WrappedNode introducer(introPort);

    // Wait for commands
    // Parse command from stdin, and have listener make a decision as to which command to perform
    while(1){
        cout << "Command: ";
        fgets(input, 100, stdin);
        sscanf(input, "%s", command); 

        if(!strcmp(command, "ADD_NODE") && attachToNode == -1){
            // Add node for each id passed in by the command 
            id = strtok(input, " ");
            while(id != NULL){
                // find an open port: make one up, try to listen on it, close it, repeat until we find one
                int done = 0;
                do {
                    port = rand() % 10000 + 49152;
                    struct sockaddr_in addr;
                    addr.sin_family = AF_INET;
                    addr.sin_port = htons(port);
                    addr.sin_addr.s_addr = INADDR_ANY;

                    int s = socket(AF_INET, SOCK_STREAM, 0);
                    if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) >= 0) {
                        done=1;
                    }
                    close(s);
                } while (!done);

                if(strcmp(id, "ADD_NODE")){
                    if(fork() == 0){
                        addNode(m, id, port, introPort, NULL, stabilizeInterval, fixInterval);
                    }
                    printf("NODE: %s PORT: %d\n", id, port);
                }

                id = strtok(NULL, " ");

            }
        } 

		// ADD FILE
		// Tells introducer to add a file with filename and data
		else if(!strcmp(command, "ADD_FILE")){
            char filename[10];
            char data[4096];
            sscanf(input, "%s %s %[^\t\n]", command, filename, data);
            cout << introducer.addFile(filename, data);
        } 
		
		// DEL FILE
		// Tell introducer to locate filename and delete from chord	
		else if(!strcmp(command, "DEL_FILE")){
            char filename[10];
            sscanf(input, "%s %s", command, filename);
            cout << introducer.delFile(filename);
        } 
		
		// GET FILE
		// Tell introducer to locate file and return with name and data
		else if(!strcmp(command, "GET_FILE")){
            char filename[10];
            sscanf(input, "%s %s", command, filename);
            cout << introducer.getFile(filename);
        }
		// GET TABLE
		// Tell introducer to find nodeID and display finger table and key table
		 else if(!strcmp(command, "GET_TABLE")){
            int id;
            sscanf(input, "%s %d", command, &id);

            cout << introducer.getTable(id);
        }

		// The rest are helper commands for us to debug 
		 else if(!strcmp(command, "GET_NODE")){
            int port;
            sscanf(input, "%s %d", command, &port);
            cout << "Getting node from port " << port << "\n";

            WrappedNode n(port);
            print_node(n.N);
        } else if(strcmp(command, "SUCC") == 0){
            int port;
            sscanf(input, "%s %d", command, &port);

            WrappedNode n = WrappedNode(port).getSuccessor();
            print_node(n.N);
        } else if(strcmp(command, "PRED") == 0){
            int port;
            sscanf(input, "%s %d", command, &port);

            WrappedNode n = WrappedNode(port).getPredecessor();
            print_node(n.N);
        } else if (!strcmp(command, "FIND_SUCC")) {
            int id;
            sscanf(input, "%s %d", command, &id);
            WrappedNode n = introducer.findSuccessor(id);
            print_node(n.N);
        } else if (!strcmp(command, "FIND_PRED")) {
            int id;
            sscanf(input, "%s %d", command, &id);
            WrappedNode n = introducer.findPredecessor(id);
            print_node(n.N);
        } else {
            cout << "No command found.\n";
        }
    }
    return 0;
}
